/************************************************************************
 *                                                                       *
 *               Copyright (C) 2002-2005  3Dlabs Inc. Ltd.               *
 *                                                                       *
 *                        All rights reserved.                           *
 *                                                                       *
 * Redistribution and use in source and binary forms, with or without    *
 * modification, are permitted provided that the following conditions    *
 * are met:                                                              *
 *                                                                       *
 *     Redistributions of source code must retain the above copyright    *
 *     notice, this list of conditions and the following disclaimer.     *
 *                                                                       *
 *     Redistributions in binary form must reproduce the above           *
 *     copyright notice, this list of conditions and the following       *
 *     disclaimer in the documentation and/or other materials provided   *
 *     with the distribution.                                            *
 *                                                                       *
 *     Neither the name of 3Dlabs Inc. Ltd. nor the names of its         *
 *     contributors may be used to endorse or promote products derived   *
 *     from this software without specific prior written permission.     *
 *                                                                       *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS   *
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT     *
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS     *
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE        *
 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, *
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,  *
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;      *
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER      *
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT    *
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN     *
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE       *
 * POSSIBILITY OF SUCH DAMAGE.                                           *
 *                                                                       *
 ************************************************************************/
/************************************************************************
 * This class creates the shader(s) to emulate the current               *
 * OpenGL Fixed State.  This class does not query the state              *
 * structures set up in the SGFixedGLState class, but rather             *
 * queries the state using the OpenGL API.  For this reason,             *
 * one can easily use this class as with an existing or new              *
 * application to extract the fixed state and generate an equivalent     *
 * shader.                                                               *
 ************************************************************************/

#include <QOpenGLFunctions>

#include "SGFixedGLState.h"
#include "SGShaderGenerator.h"

const float LOG2E = 1.442695f;

SGShaderGenerator::SGShaderGenerator(const SGFixedGLState* state)
  : m_glState(const_cast<SGFixedGLState*>(state))
{
}

/**********************************************
 * FRAGMENT SHADER
 **********************************************/

QString
SGShaderGenerator::buildFragmentShader() const
{
    QString fragShader = ""
                         "/*******************************************************\n"
                         "* Fixed.frag Fixed Function Equivalent Fragment Shader *\n"
                         "*        Automatically Generated by GLSL ShaderGen     *\n"
                         "*          https://github.com/mojocorp/ShaderGen       *\n"
                         "*******************************************************/\n";

    for (int i = 0; i < NUM_TEXTURES; i++) {
        const Texture& texture = m_glState->getTexture(i);
        if (texture.enabled) {
            fragShader += QString("uniform sampler2D texUnit%1;\n").arg(i);
        }
    }

    fragShader += "\n"
                  "void main (void) \n"
                  "{\n"
                  "    vec4 color;\n"
                  "\n"
                  "    color = gl_Color;\n\n";

    buildFragTex(fragShader);

    // No GUI Support in ShaderGen 1.0/2.0/3.0
    buildFragSeparateSpecularColor(fragShader);

    buildFragFog(fragShader);

    fragShader += "    gl_FragColor = color;\n"
                  "}\n";

    return fragShader;
}

void
SGShaderGenerator::buildFragFog(QString& str) const
{
    if (m_glState->getFogEnable()) {
        str += "\n    float fog;\n\n";

        // In previous releases of GLSL ShaderGen, the fog calculation was
        // done entirely in the shader.  To educate developers on best
        // shader programming practices, we've moved the calculation of
        //(-gl_Fog.density * LOG2E) since these values rarely change they
        // should be calculated by the application and used as constants
        // in the shader for the most efficient results.

        // if(fogMode == GL_EXP || fogMode == GL_EXP2)
        //    str += "        const float LOG2E = 1.442695;
        const int fogMode = m_glState->getFog().mode;
        const double fogDensity = m_glState->getFog().density;
        if (fogMode == GL_LINEAR) {
            str += "    fog = (gl_Fog.end - gl_FogFragCoord) * gl_Fog.scale;\n\n";
        } else if (fogMode == GL_EXP) {
            const double preComputedValue = -fogDensity * LOG2E;

            str += "\n      //fog = exp2(-gl_Fog.density * gl_FogFragCoord * "
                   "LOG2E);\n"
                   "\n      //The equation for fog allows us to move a portion of \n"
                   "        //the calculation (-gl_Fog.density * LOG2E) to the CPU\n"
                   "        //increasing the efficiency of our shader by doing a \n"
                   "        //scalar multiplication on the CPU rather than the "
                   "GPU.\n\n"
                   "        fog = exp2(gl_FogFragCoord * ";

            str += QString::number(preComputedValue);
            str += ");\n\n";

            // In the first 2 releases of ShaderGen, the fog calculation was
            // done entirely in the shader.  To educate developers on best
            // shader programming practices, we've moved the calculation of
            //(-gl_Fog.density * LOG2E) since these values rarely change they
            // should be calculated by the application and used as constants
            // in the shader for the most efficient results.

            // str += wxT("        fog = exp2(-gl_Fog.density * gl_FogFragCoord
            // * LOG2E);\n\n");
        } else if (fogMode == GL_EXP2) {
            const double preComputedValue = -fogDensity * fogDensity * LOG2E;

            str += "\n//    fog = exp2(-gl_Fog.density * gl_Fog.density *\n"
                   "    //      gl_FogFragCoord * gl_FogFragCoord * LOG2E);\n"
                   "\n  //The equation for fog allows us to move a portion of \n"
                   "    //the calculation (-gl_Fog.density * gl_Fog.density * "
                   "LOG2E)\n"
                   "    //to the CPU increasing the efficiency of our shader by "
                   "doing a \n"
                   "    //scalar multiplication on the CPU rather than the GPU.\n\n"
                   "    fog = exp2(gl_FogFragCoord * gl_FogFragCoord * ";

            str += QString::number(preComputedValue);
            str += ");\n\n";
        }
        str += "    fog = clamp(fog, 0.0, 1.0);\n\n"
               "    color = vec4(mix( vec3(gl_Fog.color), vec3(color), fog), "
               "color.a);\n\n";
    }
}

void
SGShaderGenerator::buildFragTex(QString& str) const
{
    QString fragmentTextureString;

    for (int i = 0; i < NUM_TEXTURES; i++) {
        const Texture& texture = m_glState->getTexture(i);

        if (texture.enabled) {
            switch (texture.applicationMethod) {
                case GL_MODULATE:
                    fragmentTextureString = QString("    color *= "
                                                    "texture2D(texUnit%1, "
                                                    "gl_TexCoord[%2].xy);\n\n")
                                              .arg(i).arg(i);
                    break;
                case GL_DECAL:
                    fragmentTextureString.sprintf(
                      "    vec4 texture%i = texture2D(texUnit%i, "
                      "gl_TexCoord[%i].xy);\n"
                      "    vec3 tempColor%i = mix(color.rgb, texture%i.rgb, "
                      "texture%i.a);\n"
                      "    color = vec4 (tempColor%i, color.a);\n\n",
                      i,
                      i,
                      i,
                      i,
                      i,
                      i,
                      i);
                    break;
                case GL_BLEND:
                    fragmentTextureString.sprintf("    vec4 texture%i = texture2D(texUnit%i, "
                                                  "gl_TexCoord[%i].xy);\n"
                                                  "    vec3 tempColor%i = mix(color.rgb, "
                                                  "gl_TextureEnvColor[%i].rgb, texture%i.rgb);\n"
                                                  "    color = vec4 (tempColor%i, color.a * "
                                                  "texture%i.a);\n\n",
                                                  i,
                                                  i,
                                                  i,
                                                  i,
                                                  i,
                                                  i,
                                                  i,
                                                  i);
                    break;
                case GL_REPLACE:
                    fragmentTextureString = QString("    color = "
                                                    "texture2D(texUnit%1, "
                                                    "gl_TexCoord[%2].xy);\n\n")
                                              .arg(i).arg(i);
                    break;
                case GL_ADD:
                    fragmentTextureString.sprintf("    vec4 texture%i = texture2D(texUnit%i, "
                                                  "gl_TexCoord[%i].xy);\n"
                                                  "    color.rgb += texture%i.rgb;\n"
                                                  "    color.a   *= texture%i.a;\n"
                                                  "    color = clamp(color, 0.0, 1.0);\n\n",
                                                  i,
                                                  i,
                                                  i,
                                                  i,
                                                  i);
                    break;
                case GL_COMBINE:
                    const int combineMode = texture.combineMode;
                    const int combineSrc0 = texture.combineSource0;
                    const int combineSrc1 = texture.combineSource1;
                    const int combineSrc2 = texture.combineSource2;

                    const int combineOperand0 = texture.combineOperand0;
                    const int combineOperand1 = texture.combineOperand1;
                    const int combineOperand2 = texture.combineOperand2;

                    const int combineRGBScale = texture.combineScale;

                    QString Arg0, Arg1, Arg2, Operand0, Operand1, Operand2;

                    if (combineSrc0 == GL_TEXTURE) {
                        if (combineOperand0 == GL_SRC_COLOR) {
                            Arg0.sprintf("    vec4 texture%iArg0 = texture2D(texUnit%i, "
                                         "gl_TexCoord[%i].xy);\n"
                                         "    vec4 texUnit%iArg0 = texture%iArg0;\n",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        } else if (combineOperand0 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg0.sprintf("    vec4 texture%iArg0 = "
                                         "texture2D(texUnit%i,gl_TexCoord[%i]."
                                         "xy);\n"
                                         "        vec4 texUnit%iArg0 = "
                                         "vec4(1.0) - texture%iArg0;\n",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        }
                    } else if (combineSrc0 == GL_CONSTANT) {
                        if (combineOperand0 == GL_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        } else if (combineOperand0 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = vec4(1.0) - "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        }
                    } else if (combineSrc0 == GL_PRIMARY_COLOR) {
                        if (combineOperand0 == GL_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = gl_Color;").arg(i);
                        } else if (combineOperand0 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = vec4(1.0) - gl_Color;").arg(i);
                        }
                    } else if (combineSrc0 == GL_PREVIOUS) {
                        if (combineOperand0 == GL_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = color;").arg(i);
                        } else if (combineOperand0 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg0 = QString("    vec4 texUnit%1Arg0 = vec4(1.0) - color;").arg(i);
                        }
                    }
                    if (combineSrc1 == GL_TEXTURE) {
                        if (combineOperand1 == GL_SRC_COLOR) {
                            Arg1.sprintf("    vec4 texture%iArg1 = "
                                         "texture2D(texUnit%i,gl_TexCoord[%i].xy);\n"
                                         "    vec4 texUnit%iArg1 = texture%iArg1;\n",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        } else if (combineOperand1 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg1.sprintf("    vec4 texture%iArg1 = "
                                         "texture2D(texUnit%i,gl_TexCoord[%i]."
                                         "xy);\n"
                                         "    vec4 texUnit%iArg1 = vec4(1.0) - "
                                         "texture%iArg1;\n",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        }
                    } else if (combineSrc1 == GL_CONSTANT) {
                        if (combineOperand1 == GL_SRC_COLOR) {
                            Arg1 = QString("    vec4 texUnit%1Arg1 = "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        } else if (combineOperand1 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg1 = QString("    vec4 texUnit%1Arg1 = vec4(1.0) - "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        }
                    } else if (combineSrc1 == GL_PRIMARY_COLOR) {
                        if (combineOperand1 == GL_SRC_COLOR) {
                            Arg1 = QString("    vec4 texUnit%1Arg1 = gl_Color;\n").arg(i);
                        } else if (combineOperand1 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg1 = QString("    vec4 texUnit%1Arg1 = vec4(1.0) - "
                                           "gl_Color;\n")
                                     .arg(i);
                        }
                    } else if (combineSrc1 == GL_PREVIOUS) {
                        if (combineOperand1 == GL_SRC_COLOR) {
                            Arg1 = QString("    vec4 texUnit%1Arg1 = color;\n").arg(i);
                        } else if (combineOperand1 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg1 = QString("   vec4 texUnit%1Arg1 = vec4(1.0) - color;\n").arg(i);
                        }
                    }
                    if (combineSrc2 == GL_TEXTURE) {
                        if (combineOperand2 == GL_SRC_COLOR) {
                            Arg2.sprintf("    vec4 texture%iArg2 = "
                                         "texture2D(texUnit%i,gl_TexCoord[%i].xy);\n"
                                         "    vec4 texUnit%iArg2 = texture%iArg2\n;",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        } else if (combineOperand2 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg2.sprintf("    vec4 texture%iArg2 = "
                                         "texture2D(texUnit%i,gl_TexCoord[%i]."
                                         "xy);\n"
                                         "    vec4 texUnit%iArg2 = vec4(1.0) - "
                                         "texture%iArg2\n;",
                                         i,
                                         i,
                                         i,
                                         i,
                                         i);
                        }
                    } else if (combineSrc2 == GL_CONSTANT) {
                        if (combineOperand2 == GL_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        } else if (combineOperand2 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = vec4(1.0) - "
                                           "gl_TextureEnvColor[%2];\n")
                                     .arg(i).arg(i);
                        }
                    } else if (combineSrc2 == GL_PRIMARY_COLOR) {
                        if (combineOperand2 == GL_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = gl_Color;\n").arg(i);
                        } else if (combineOperand2 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = vec4(1.0) - "
                                           "gl_Color;\n")
                                     .arg(i);
                        }
                    } else if (combineSrc2 == GL_PREVIOUS) {
                        if (combineOperand2 == GL_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = color;\n").arg(i);
                        } else if (combineOperand2 == GL_ONE_MINUS_SRC_COLOR) {
                            Arg2 = QString("    vec4 texUnit%1Arg2 = vec4(1.0) - color;\n").arg(i);
                        }
                    }
                    switch (combineMode) {
                        case GL_REPLACE:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + QString("    color = clamp(%1 * "
                                                           "texUnit%2Arg0, 0.0, "
                                                           "1.0);\n").arg((float)combineRGBScale).arg(i);
                            } else {
                                fragmentTextureString = Arg0 + QString("    color = "
                                                                       "clamp(texUnit%1Arg0, 0.0, "
                                                                       "1.0);\n")
                                                                 .arg(i);
                            }
                            break;
                        case GL_MODULATE:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString("    color = clamp(%1 * texUnit%2Arg0 * "
                                          "texUnit%3Arg1, 0.0, 1.0);\n")
                                    .arg((float)combineRGBScale).arg(i).arg(i);
                            } else {
                                fragmentTextureString = Arg0 + Arg1 +
                                                        QString("    color = clamp(texUnit%1Arg0 * "
                                                                "texUnit%2Arg1, 0.0, 1.0);\n")
                                                          .arg(i).arg(i);
                            }
                            break;
                        case GL_ADD:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString("    color = clamp(%1 * "
                                          "vec4(texUnit%2Arg0 + texUnit%3Arg1), 0.0, "
                                          "1.0);\n")
                                    .arg((float)combineRGBScale).arg(i).arg(i);
                            } else {
                                fragmentTextureString = Arg0 + Arg1 +
                                                        QString("    color = clamp(texUnit%1Arg0 + "
                                                                "texUnit%2Arg1, 0.0, 1.0);\n")
                                                          .arg(i).arg(i);
                            }
                            break;
                        case GL_ADD_SIGNED:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString("    color = clamp(%1 * "
                                          "vec4(texUnit%2Arg0 + texUnit%3Arg1 - "
                                          "vec4(0.5)), 0.0, 1.0);\n")
                                    .arg((float)combineRGBScale).arg(i).arg(i);
                            } else {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString("    color = clamp(texUnit%1Arg0 + "
                                          "texUnit%2Arg1 - vec4(0.5), 0.0, 1.0);\n")
                                    .arg(i).arg(i);
                            }
                            break;
                        case GL_INTERPOLATE:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 + Arg2 +
                                  QString().sprintf("    color = clamp(%.1f * (texUnit%iArg0 * "
                                                    "texUnit%iArg2"
                                                    " + texUnit%iArg1 * (vec4(1.0) - "
                                                    "texUnit%iArg2)), 0.0, 1.0);\n",
                                                    (float)combineRGBScale,
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            } else {
                                fragmentTextureString =
                                  Arg0 + Arg1 + Arg2 +
                                  QString().sprintf("    color = clamp(texUnit%iArg0 * "
                                                    "texUnit%iArg2"
                                                    " + texUnit%iArg1 * (vec4(1.0) - "
                                                    "texUnit%iArg2), 0.0, 1.0);\n",
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            }
                            break;
                        case GL_SUBTRACT:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString("    color = clamp(%1 * (texUnit%2Arg0 - "
                                          "texUnit%3Arg1), 0.0, 1.0);\n")
                                    .arg((float)combineRGBScale).arg(i).arg(i);
                            } else {
                                fragmentTextureString = Arg0 + Arg1 +
                                                        QString("    color = clamp(texUnit%1Arg0 - "
                                                                "texUnit%2Arg1, 0.0, 1.0);\n")
                                                          .arg(i).arg(i);
                            }
                            break;
                        case GL_DOT3_RGB:
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString().sprintf("    color = vec4(clamp(%.1f * 4.0 * (( "
                                                    "texUnit%iArg0.r - 0.5) * "
                                                    "(texUnit%iArg1.r- 0.5)"
                                                    " + (texUnit%iArg0.g - 0.5) * "
                                                    "(texUnit%iArg1.g - 0.5) + "
                                                    "(texUnit%iArg0.b - 0.5) * "
                                                    "(texUnit%iArg1.b - 0.5), 0.0, 1.0));\n",
                                                    (float)combineRGBScale,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            } else {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString().sprintf("    color = vec4(clamp(4.0 * (( "
                                                    "texUnit%iArg0.r - 0.5) * "
                                                    "(texUnit%iArg1.r- 0.5)"
                                                    " + (texUnit%iArg0.g - 0.5) * "
                                                    "(texUnit%iArg1.g - 0.5) + "
                                                    "(texUnit%iArg0.b - 0.5) * "
                                                    "(texUnit%iArg1.b - 0.5)), 0.0, 1.0));\n",
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            }
                            break;
                        case GL_DOT3_RGBA:
                            // Not implemented in ShaderGen GUI.
                            if (combineRGBScale > 1) {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString().sprintf("    color = vec4(clamp(%.1f * 4.0 * (( "
                                                    "texUnit%iArg0.r - 0.5) * "
                                                    "(texUnit%iArg1.r- 0.5)"
                                                    " + (texUnit%iArg0.g - 0.5) * "
                                                    "(texUnit%iArg1.g - 0.5) + "
                                                    "(texUnit%iArg0.b - 0.5) * "
                                                    "(texUnit%iArg1.b - 0.5)), 0.0, 1.0));\n",
                                                    (float)combineRGBScale,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            } else {
                                fragmentTextureString =
                                  Arg0 + Arg1 +
                                  QString().sprintf("    color = vec4(clamp(4.0 * (( "
                                                    "texUnit%iArg0.r - 0.5) * "
                                                    "(texUnit%iArg1.r- 0.5)"
                                                    " + (texUnit%iArg0.g - 0.5) * "
                                                    "(texUnit%iArg1.g - 0.5) + "
                                                    "(texUnit%iArg0.b - 0.5) * "
                                                    "(texUnit%iArg1.b - 0.5)), 0.0, 1.0));\n",
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i,
                                                    i);
                            }
                            break;
                    }
                    break;
            }
            str += fragmentTextureString;
        }
    }
}

void
SGShaderGenerator::buildFragSeparateSpecularColor(QString& str) const
{
    if (m_glState->getSeparateSpecularColorEnable()) {
        str += "    color += gl_SecondaryColor;\n"
               "    color = clamp(color, 0.0, 1.0);\n\n";
    }
}
/*******************************************
 * VERTEX SHADER
 ********************************************/

QString
SGShaderGenerator::buildVertexShader() const
{
    QString topVertShader = ""
                            "/*******************************************************\n"
                            "*  Fixed.vert Fixed Function Equivalent Vertex Shader  *\n"
                            "*        Automatically Generated by GLSL ShaderGen     *\n"
                            "*          https://github.com/mojocorp/ShaderGen       *\n"
                            "*******************************************************/\n";

    topVertShader += "vec4 Ambient;\nvec4 Diffuse;\nvec4 Specular;\n\n";

    QString bottomVertShader = "";

    bool fLightPoint = false, fLightSpot = false, fLightDir = false, fLightDirSpot = false;
    bool fMapSphere = false, fMapReflection = false;

    buildFuncFnormal(bottomVertShader);

    // The for loop counts DOWN to find last texture unit in use.
    // This is necessary for the BuildTexCoord function to handle
    //  multiple texture coordinates correctly.
    for (int i = NUM_TEXTURES - 1; i >= 0; i--) {
        const Texture& texture = m_glState->getTexture(i);
        if (texture.enabled) {
            buildTexCoord(bottomVertShader, fMapReflection, fMapSphere);
            break;
        }
    }
    if (m_glState->getLightingEnable())
        buildLightCode(bottomVertShader, fLightPoint, fLightSpot, fLightDir, fLightDirSpot);

    buildVertMain(bottomVertShader);

    if (fLightPoint) {
        addFuncLightPoint(topVertShader);
    }
    if (fLightSpot) {
        addFuncLightSpot(topVertShader);
    }
    if (fLightDir) {
        addFuncLightDirectional(topVertShader);
    }
    if (fLightDirSpot) {
        addFuncLightSpotDirection(topVertShader);
    }
    if (fMapSphere) {
        addFuncSphereMap(topVertShader);
    }
    if (fMapReflection) {
        addFuncReflectionMap(topVertShader);
    }
    if (m_glState->getFogEnable()) {
        buildFuncFog(topVertShader);
    }
    return topVertShader + bottomVertShader;
}

void
SGShaderGenerator::buildLightCode(QString& str,
                                  bool& fLightPoint,
                                  bool& fLightSpot,
                                  bool& fLightDir,
                                  bool& fLightDirSpot) const
{
    const bool lightModelTwoSided = m_glState->getTwoSidedLightingEnable();

    const bool localView = m_glState->getLocalViewerLightingEnable();

    if (m_glState->getLightingEnable()) {

        str += "\nvoid flight(in vec3 normal, in vec4 ecPosition, float alphaFade)\n"
               "{\n"
               "    vec4 color;\n"
               "    vec3 ecPosition3;\n"
               "    vec3 eye;\n\n"
               "    ecPosition3 = (vec3 (ecPosition)) / ecPosition.w;\n";

        if (localView) {
            str += "    eye = -normalize(ecPosition3);\n\n";
        } else {
            str += "    eye = vec3 (0.0, 0.0, 1.0);\n\n";
        }

        str += "    // Clear the light intensity accumulators\n"
               "    Ambient  = vec4 (0.0);\n"
               "    Diffuse  = vec4 (0.0);\n"
               "    Specular = vec4 (0.0);\n\n";

        for (int i = 0; i < NUM_LIGHTS; i++) {
            const Light& light = m_glState->getLight(i);
            if (light.enabled) {
                const QVector4D& v = light.position;
                const float spotCut = light.spotCutoff;

                if (v[3] == 0.0) {
                    if (spotCut != DEFAULT_SPOT_CUT) {
                        str += QString("    infiniteSpotLight(%1, normal);\n\n").arg(i);
                        fLightDirSpot = true;
                    } else {
                        str += QString("    directionalLight(%1, normal);\n\n").arg(i);
                        fLightDir = true;
                    }
                } else {
                    if (spotCut == DEFAULT_SPOT_CUT) {
                        str += QString("    pointLight(%1, normal, eye, ecPosition3);\n\n").arg(i);
                        fLightPoint = true;
                    } else {
                        str += QString("    spotLight(%1, normal, eye, ecPosition3);\n\n").arg(i);
                        fLightSpot = true;
                    }
                }
            }
        }

        str += "    color = gl_FrontLightModelProduct.sceneColor +\n"
               "            Ambient  * gl_FrontMaterial.ambient +\n"
               "            Diffuse  * gl_FrontMaterial.diffuse;\n";

        if (m_glState->getSeparateSpecularColorEnable()) {
            str += "    gl_FrontSecondaryColor = Specular * "
                   "gl_FrontMaterial.specular;\n";
        } else {
            str += "    color += Specular * gl_FrontMaterial.specular;\n";
        }

        str += "    color = clamp( color, 0.0, 1.0 );\n";

        str += "    gl_FrontColor = color;\n";

        if (lightModelTwoSided) {
            str += "    // Invert the normal for these lighting calculations\n"
                   "    normal = -normal;\n"
                   "    //Clear the light intensity accumulators\n"
                   "    Ambient  = vec4 (0.0);\n"
                   "    Diffuse  = vec4 (0.0);\n"
                   "    Specular = vec4 (0.0);\n\n";

            for (int i = 0; i < NUM_LIGHTS; i++) {
                const Light& light = m_glState->getLight(i);
                if (light.enabled) {
                    const QVector4D& v = light.position;

                    if (v[3] == 0.0) {
                        str += QString("    directionalLight(%1, normal);\n\n").arg(i);
                        fLightDir = true;
                    } else {
                        const float spotCut = light.spotCutoff;
                        if (spotCut == 180.0) {
                            str += QString("    pointLight(%1, normal, "
                                           "eye, ecPosition3);\n\n")
                                     .arg(i);
                            fLightPoint = true;
                        } else {
                            str += QString("    spotLight(%1, normal, "
                                           "eye, ecPosition3);\n\n")
                                     .arg(i);
                            fLightSpot = true;
                        }
                    }
                }
            }

            str += "\n"
                   "    color = gl_BackLightModelProduct.sceneColor +\n"
                   "        Ambient * gl_BackMaterial.ambient +\n"
                   "        Diffuse * gl_BackMaterial.diffuse;\n";

            if (m_glState->getSeparateSpecularColorEnable())
                str += "    gl_BackSecondaryColor = Specular * "
                       "gl_BackMaterial.specular;\n";
            else
                str += "    color += Specular * gl_BackMaterial.specular;\n";

            str += "    gl_BackColor = color;\n";
        }

        str += "\n"
               "    gl_FrontColor.a *= alphaFade;\n";

        if (lightModelTwoSided) {
            str += "    gl_FrontSecondaryColor.a *= alphaFade;\n"
                   "    gl_BackColor.a *= alphaFade;\n"
                   "    gl_BackSecondaryColor.a *= alphaFade;\n";
        }
        str += "}\n";
    }
}

void
SGShaderGenerator::buildFuncFnormal(QString& str) const
{
    if (m_glState->getLightingEnable() || m_glState->getTextureEnable()) {
        str += "\n"
               "vec3 fnormal(void)\n"
               "{\n"
               "    //Compute the normal \n"
               "    vec3 normal = gl_NormalMatrix * gl_Normal;\n";

        if (m_glState->getNormalizeEnable()) {
            str += "    normal = normalize(normal);\n";
        }
        if (m_glState->getRescaleNormalEnable()) {
            str += "    normal = normal * gl_NormalScale;\n";
            str += "    return normal;\n"
                   "}\n";
        } else {
            str += "    return normal;\n"
                   "}\n";
        }
    }
}

void
SGShaderGenerator::buildFuncFog(QString& str) const
{
    str += "\n"
           "float ffog(in float ecDistance)\n"
           "{\n";

    if (m_glState->getFog().source == GL_FOG_COORD) {
        str += "    return gl_FogCoord;\n";
    } else {
        str += "    return(abs(ecDistance));\n";
    }
    str += "}\n";
}

void
SGShaderGenerator::buildTexCoord(QString& str, bool& fMapReflection, bool& fMapSphere) const
{
    str += "\nvoid ftexgen(in vec3 normal, in vec4 ecPosition)\n"
           "{\n";

    QString texCoordString, tempStringA, tempStringB;

    for (int i = 0; i < NUM_TEXTURES; i++) {
        const Texture& texture = m_glState->getTexture(i);
        if (texture.texGen) {
            if (texture.coordinateGeneration == GL_REFLECTION_MAP) {
                if (!fMapReflection && !fMapSphere) {
                    tempStringA = "    vec3 ecPosition3;"
                                  "    ecPosition3 = (vec3(ecPosition))/ecPosition.w;\n";
                }
                if (!fMapReflection) {
                    tempStringB = "    vec3 reflection = reflectionMap( "
                                  "normal, ecPosition3 );\n";
                    if (!fMapSphere) {
                        texCoordString =
                          tempStringA + tempStringB +
                          QString("    gl_TexCoord[%1] = vec4(reflection, 1.0); \n").arg(i);
                    } else {
                        texCoordString =
                          tempStringB +
                          QString("    gl_TexCoord[%1] = vec4(reflection, 1.0); \n").arg(i);
                    }
                    fMapReflection = true;
                } else {
                    texCoordString =
                      QString("    gl_TexCoord[%1] = vec4(reflection, 1.0); \n").arg(i);
                }
            } else if (texture.coordinateGeneration == GL_SPHERE_MAP) {
                if (!fMapSphere && !fMapReflection) {
                    tempStringA = "    vec3 ecPosition3;\n"
                                  "    ecPosition3 = (vec3(ecPosition))/ecPosition.w;\n";
                }
                if (!fMapSphere) {
                    tempStringB = "    vec2 sMap = sphereMap(normal, ecPosition3);\n";

                    if (!fMapReflection) {
                        texCoordString =
                          tempStringA + tempStringB +
                          QString("    gl_TexCoord[%1] = vec4(sMap, 0.0, 1.0);\n").arg(i);
                    } else {
                        texCoordString =
                          tempStringB +
                          QString("    gl_TexCoord[%1] = vec4(sMap, 0.0, 1.0);\n").arg(i);
                    }
                    fMapSphere = true;
                } else {
                    texCoordString =
                      QString("    gl_TexCoord[%i] = vec4(sMap, 0.0, 1.0);\n").arg(i);
                }
            } else if (texture.coordinateGeneration == GL_EYE_LINEAR) {
                texCoordString.sprintf("\n    gl_TexCoord[%i].s = dot( "
                                       "ecPosition, gl_EyePlaneS[%i] );\n"
                                       "    gl_TexCoord[%i].t = dot( "
                                       "ecPosition, gl_EyePlaneT[%i] );\n"
                                       "    gl_TexCoord[%i].p = dot( "
                                       "ecPosition, gl_EyePlaneR[%i] );\n"
                                       "    gl_TexCoord[%i].q = dot( "
                                       "ecPosition, gl_EyePlaneQ[%i] );\n",
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i);
            } else if (texture.coordinateGeneration == GL_OBJECT_LINEAR) {
                texCoordString.sprintf("\n    gl_TexCoord[%i].s = dot( "
                                       "gl_Vertex, gl_ObjectPlaneS[%i] );\n"
                                       "    gl_TexCoord[%i].t = dot( "
                                       "gl_Vertex, gl_ObjectPlaneT[%i] );\n"
                                       "    gl_TexCoord[%i].p = dot( "
                                       "gl_Vertex, gl_ObjectPlaneR[%i] );\n"
                                       "    gl_TexCoord[%i].q = dot( "
                                       "gl_Vertex, gl_ObjectPlaneQ[%i] );\n",
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i,
                                       i);
            } else if (texture.coordinateGeneration == GL_NORMAL_MAP) {
                texCoordString = QString("\n    gl_TexCoord[%1] = vec4( normal, 1.0 );\n").arg(i);
            }
        } else {
            texCoordString = QString("\n"
                                     "    gl_TexCoord[%1] = gl_MultiTexCoord%2;\n")
                               .arg(i).arg(i);
        }
        str += texCoordString;
    }

    str += "}\n";
}

// This functionality is not yet implemented in the GLSL ShaderGen GUI, it may
//  be added at a later date.  There has been no testing done with regards to
//  all "point" functions.
void
SGShaderGenerator::buildFuncPoint(QString& str) const
{
    str = "    float fpoint(in float ecDistance, out float fadeFactor)\n"
          "    {\n"
          "        float derivedSize;\n"
          "        derivedSize = gl_Point.distanceConstantAttenuation +\n"
          "            (gl_Point.distanceLinearAttenuation * ecDistance) +\n"
          "            (gl_Point.distanceQuadraticAttenuation * ecDistance * "
          "ecDistance);\n"
          "        derivedSize = gl_Point.size * inversesqrt(derivedSize);\n"
          "        derivedSize = clamp(derivedSize, gl_Point.sizeMin, "
          "gl_Point.sizeMax);\n";

    if (glIsEnabled(GL_MULTISAMPLE)) {
        str += "        {\n"
               "            derivedSize = max(derivedSize, "
               "gl_Point.fadeThresholdSize);\n"
               "            if (derivedSize >= gl_Point.fadeThresholdSize)\n"
               "                fadeFactor = 1.0;\n"
               "            else\n"
               "            {\n"
               "                float ratio = "
               "derivedSize/gl_Point.fadeThresholdSize;\n"
               "                fadeFactor  = ratio * ratio;\n"
               "            }\n"
               "        }\n";
    }

    str += "    return derivedSize;\n    }\n";
}

void
SGShaderGenerator::buildVertMain(QString& str) const
{
    const bool lighting = m_glState->getLightingEnable();

    str += "\n\n"
           "void main (void)\n"
           "{\n";

    for (int i = 0; i < NUM_TEXTURES; i++) {
        const Texture& texture = m_glState->getTexture(i);
        if (texture.enabled || lighting) {
            str += "    vec3  transformedNormal;\n"
                   "    float alphaFade = 1.0;\n\n";
            break;
        }
    }
    str += "    // Eye-coordinate position of vertex, needed in various "
           "calculations\n"
           "    vec4 ecPosition = gl_ModelViewMatrix * gl_Vertex;\n\n"
           "    // Do fixed functionality vertex transform\n"
           "    gl_Position = gl_ProjectionMatrix * gl_ModelViewMatrix * "
           "gl_Vertex;\n";

    for (int i = 0; i < NUM_TEXTURES; i++) {
        const Texture& texture = m_glState->getTexture(i);
        if (texture.enabled || lighting) {
            str += "    transformedNormal = fnormal();\n";
            break;
        }
    }

    //
    // Query and support point parameters here.
    //

    if (m_glState->getLightingEnable()) {
        str += "    flight(transformedNormal, ecPosition, alphaFade);\n";
    }
    /*else
    {
        //str += wxT("    gl_FrontColor = gl_Color;\n");
    }*/

    if (m_glState->getFogEnable()) {
        str += "    gl_FogFragCoord = ffog(ecPosition.z);\n";
    }

    if (m_glState->getTextureEnable()) {
        str += "    ftexgen(transformedNormal, ecPosition);\n";
    }

    if (glIsEnabled(GL_CLIP_PLANE0) || glIsEnabled(GL_CLIP_PLANE1) || glIsEnabled(GL_CLIP_PLANE2) ||
        glIsEnabled(GL_CLIP_PLANE3) || glIsEnabled(GL_CLIP_PLANE4) || glIsEnabled(GL_CLIP_PLANE5)) {
        str += "    gl_ClipVertex = ecPosition;\n";
    }

    str += "}";
}

/*****************************************************************
 * STATIC METHODS TO ADD PREDEFINED FUNCTIONS
 *****************************************************************/

void
SGShaderGenerator::addFuncLightDirectional(QString& str) const
{
    str += "\nvoid directionalLight(in int i, in vec3 normal)\n"
           "{\n"
           "   float nDotVP;         // normal . light direction\n"
           "   float nDotHV;         // normal . light half vector\n"
           "   float pf;             // power factor\n\n"
           "   nDotVP = max(0.0, dot(normal, normalize(vec3 "
           "(gl_LightSource[i].position))));\n"
           "   nDotHV = max(0.0, dot(normal, vec3 "
           "(gl_LightSource[i].halfVector)));\n\n"
           "   if (nDotVP == 0.0)\n"
           "   {\n"
           "       pf = 0.0;\n"
           "   }\n"
           "   else\n"
           "   {\n"
           "       pf = pow(nDotHV, gl_FrontMaterial.shininess);\n\n"
           "   }\n"
           "   Ambient  += gl_LightSource[i].ambient;\n"
           "   Diffuse  += gl_LightSource[i].diffuse * nDotVP;\n"
           "   Specular += gl_LightSource[i].specular * pf;\n"
           "}\n";
}

void
SGShaderGenerator::addFuncLightPoint(QString& str) const
{
    str += "\nvoid pointLight(in int i, in vec3 normal, in vec3 eye, in vec3 "
           "ecPosition3)\n"
           "{\n"
           "   float nDotVP;       // normal . light direction\n"
           "   float nDotHV;       // normal . light half vector\n"
           "   float pf;           // power factor\n"
           "   float attenuation;  // computed attenuation factor\n"
           "   float d;            // distance from surface to light source\n"
           "   vec3  VP;           // direction from surface to light position\n"
           "   vec3  halfVector;   // direction of maximum highlights\n\n"
           "   // Compute vector from surface to light position\n"
           "   VP = vec3 (gl_LightSource[i].position) - ecPosition3;\n\n"
           "   // Compute distance between surface and light position\n"
           "   d = length(VP);\n\n"
           "   // Normalize the vector from surface to light position\n"
           "   VP = normalize(VP);\n\n"
           "   // Compute attenuation\n"
           "   attenuation = 1.0 / (gl_LightSource[i].constantAttenuation +\n"
           "                        gl_LightSource[i].linearAttenuation * d +\n"
           "                        gl_LightSource[i].quadraticAttenuation * d * "
           "d);\n\n"
           "   halfVector = normalize(VP + eye);\n\n"
           "   nDotVP = max(0.0, dot(normal, VP));\n"
           "   nDotHV = max(0.0, dot(normal, halfVector));\n\n"
           "   if (nDotVP == 0.0)\n"
           "   {\n"
           "       pf = 0.0;\n"
           "   }\n"
           "   else\n"
           "   {\n"
           "       pf = pow(nDotHV, gl_FrontMaterial.shininess);\n\n"
           "   }\n"
           "   Ambient  += gl_LightSource[i].ambient * attenuation;\n"
           "   Diffuse  += gl_LightSource[i].diffuse * nDotVP * attenuation;\n"
           "   Specular += gl_LightSource[i].specular * pf * attenuation;\n"
           "}\n";
}

void
SGShaderGenerator::addFuncLightSpot(QString& str) const
{
    str += "\nvoid spotLight(in int i, in vec3 normal, in vec3 eye, in vec3 "
           "ecPosition3)\n"
           "{\n"
           "   float nDotVP;            // normal . light direction\n"
           "   float nDotHV;            // normal . light half vector\n"
           "   float pf;                // power factor\n"
           "   float spotDot;           // cosine of angle between spotlight\n"
           "   float spotAttenuation;   // spotlight attenuation factor\n"
           "   float attenuation;       // computed attenuation factor\n"
           "   float d;                 // distance from surface to light source\n"
           "   vec3  VP;                // direction from surface to light "
           "position\n"
           "   vec3  halfVector;        // direction of maximum highlights\n\n"
           "   // Compute vector from surface to light position\n"
           "   VP = vec3 (gl_LightSource[i].position) - ecPosition3;\n\n"
           "   // Compute distance between surface and light position\n"
           "   d = length(VP);\n\n"
           "   // Normalize the vector from surface to light position\n"
           "   VP = normalize(VP);\n\n"
           "   // Compute attenuation\n"
           "   attenuation = 1.0 / (gl_LightSource[i].constantAttenuation +\n"
           "       gl_LightSource[i].linearAttenuation * d +\n"
           "       gl_LightSource[i].quadraticAttenuation * d * d);\n\n"
           "   // See if point on surface is inside cone of illumination\n"
           "   spotDot = dot(-VP, normalize(gl_LightSource[i].spotDirection));\n\n"
           "   if (spotDot < gl_LightSource[i].spotCosCutoff)\n"
           "   {\n"
           "       spotAttenuation = 0.0; // light adds no contribution\n"
           "   }\n"
           "   else\n"
           "   {\n"
           "       spotAttenuation = pow(spotDot, "
           "gl_LightSource[i].spotExponent);\n\n"
           "   }\n"
           "   // Combine the spotlight and distance attenuation.\n"
           "   attenuation *= spotAttenuation;\n\n"
           "   halfVector = normalize(VP + eye);\n\n"
           "   nDotVP = max(0.0, dot(normal, VP));\n"
           "   nDotHV = max(0.0, dot(normal, halfVector));\n\n"
           "   if (nDotVP == 0.0)\n"
           "   {\n"
           "       pf = 0.0;\n"
           "   }\n"
           "   else\n"
           "   {\n"
           "       pf = pow(nDotHV, gl_FrontMaterial.shininess);\n\n"
           "   }\n"
           "   Ambient  += gl_LightSource[i].ambient * attenuation;\n"
           "   Diffuse  += gl_LightSource[i].diffuse * nDotVP * attenuation;\n"
           "   Specular += gl_LightSource[i].specular * pf * attenuation;\n\n"
           "}\n";
}

void
SGShaderGenerator::addFuncLightSpotDirection(QString& str) const
{
    str += "\nvoid infiniteSpotLight(in int i, in vec3 normal)\n"
           "{\n"
           "   float nDotVP;         // normal . light direction\n"
           "   float nDotHV;         // normal . light half vector\n"
           "   float pf;             // power factor\n"
           "   float spotAttenuation;\n"
           "   vec3  Ppli;\n"
           "   vec3  Sdli;\n\n"
           "   nDotVP = max(0.0, dot(normal, normalize(vec3 "
           "(gl_LightSource[i].position))));\n"
           "   nDotHV = max(0.0, dot(normal, vec3 "
           "(gl_LightSource[i].halfVector)));\n\n"
           "   Ppli = -normalize(vec3(gl_LightSource[i].position));\n"
           "   Sdli = normalize(vec3(gl_LightSource[i].spotDirection));\n\n"
           "   spotAttenuation = pow(dot(Ppli, Sdli), "
           "gl_LightSource[i].spotExponent);\n"
           "   if (nDotVP == 0.0)\n"
           "   {\n"
           "       pf = 0.0;\n"
           "   }\n"
           "   else\n"
           "   {\n"
           "       pf = pow(nDotHV, gl_FrontMaterial.shininess);\n\n"
           "   }\n"
           "   Ambient  += gl_LightSource[i].ambient * spotAttenuation;\n"
           "   Diffuse  += gl_LightSource[i].diffuse * nDotVP * spotAttenuation;\n"
           "   Specular += gl_LightSource[i].specular * pf * spotAttenuation;\n"
           "}\n";
}

void
SGShaderGenerator::addFuncSphereMap(QString& str) const
{
    str += "\nvec2 sphereMap(in vec3 normal, in vec3 ecPosition3)\n"
           "{\n"
           "   float m;\n"
           "   vec3 r, u;\n"
           "   u = normalize(ecPosition3);\n"
           "   r = reflect(u, normal);\n"
           "   m = 2.0 * sqrt(r.x * r.x + r.y * r.y + (r.z + 1.0) * (r.z + 1.0));\n"
           "   return vec2 (r.x / m + 0.5, r.y / m + 0.5);\n"
           "}\n";
}

void
SGShaderGenerator::addFuncReflectionMap(QString& str) const
{
    str += "\nvec3 reflectionMap(in vec3 normal, in vec3 ecPosition3)\n"
           "{\n"
           "   float NdotU, m;\n"
           "   vec3 u;\n"
           "   u = normalize(ecPosition3);\n"
           "   return (reflect(u, normal));\n"
           "}\n";
}
